﻿using System;

namespace MSPToolkit.Compression
{
    internal class Deflater
    {
        public static int BEST_COMPRESSION = 9;

        public static int BEST_SPEED = 1;

        public static int DEFAULT_COMPRESSION = -1;

        public static int NO_COMPRESSION = 0;

        public static int DEFLATED = 8;

        private static int IS_SETDICT = 0x01;
        private static int IS_FLUSHING = 0x04;
        private static int IS_FINISHING = 0x08;

        private static int INIT_STATE = 0x00;
        private static int SETDICT_STATE = 0x01;
        private static int BUSY_STATE = 0x10;
        private static int FLUSHING_STATE = 0x14;
        private static int FINISHING_STATE = 0x1c;
        private static int FINISHED_STATE = 0x1e;
        private static int CLOSED_STATE = 0x7f;

        private int level;
        private bool noHeader;
        private int state;
        private int totalOut;

        private DeflaterPending pending;

        private DeflaterEngine engine;

        public Deflater()
            : this(DEFAULT_COMPRESSION, false)
        {

        }

        public Deflater(int lvl)
            : this(lvl, false)
        {

        }

        public Deflater(int lvl, bool nowrap)
        {
            if (lvl == DEFAULT_COMPRESSION)
            {
                lvl = 6;
            }
            else if (lvl < NO_COMPRESSION || lvl > BEST_COMPRESSION)
            {
                throw new ArgumentOutOfRangeException("lvl");
            }

            pending = new DeflaterPending();
            engine = new DeflaterEngine(pending);
            this.noHeader = nowrap;
            SetStrategy(DeflateStrategy.Default);
            SetLevel(lvl);
            Reset();
        }

        public void Reset()
        {
            state = (noHeader ? BUSY_STATE : INIT_STATE);
            totalOut = 0;
            pending.Reset();
            engine.Reset();
        }

        public int Adler
        {
            get
            {
                return engine.Adler;
            }
        }

        public int TotalIn
        {
            get
            {
                return engine.TotalIn;
            }
        }

        public int TotalOut
        {
            get
            {
                return totalOut;
            }
        }

        public void Flush()
        {
            state |= IS_FLUSHING;
        }

        public void Finish()
        {
            state |= IS_FLUSHING | IS_FINISHING;
        }

        public bool IsFinished
        {
            get
            {
                return state == FINISHED_STATE && pending.IsFlushed;
            }
        }

        public bool IsNeedingInput
        {
            get
            {
                return engine.NeedsInput();
            }
        }

        public void SetInput(byte[] input)
        {
            SetInput(input, 0, input.Length);
        }

        public void SetInput(byte[] input, int off, int len)
        {
            if ((state & IS_FINISHING) != 0)
            {
                throw new InvalidOperationException("finish()/end() already called");
            }
            engine.SetInput(input, off, len);
        }

        public void SetLevel(int lvl)
        {
            if (lvl == DEFAULT_COMPRESSION)
            {
                lvl = 6;
            }
            else if (lvl < NO_COMPRESSION || lvl > BEST_COMPRESSION)
            {
                throw new ArgumentOutOfRangeException("lvl");
            }


            if (level != lvl)
            {
                level = lvl;
                engine.SetLevel(lvl);
            }
        }

        public void SetStrategy(DeflateStrategy stgy)
        {
            engine.Strategy = stgy;
        }

        public int Deflate(byte[] output, int offset, int length)
        {
            int origLength = length;

            if (state == CLOSED_STATE)
            {
                throw new InvalidOperationException("Deflater closed");
            }

            if (state < BUSY_STATE)
            {
                int header = (DEFLATED +
                    ((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8;
                int level_flags = (level - 1) >> 1;
                if (level_flags < 0 || level_flags > 3)
                {
                    level_flags = 3;
                }
                header |= level_flags << 6;
                if ((state & IS_SETDICT) != 0)
                {
                    /* Dictionary was set */
                    header |= DeflaterConstants.PRESET_DICT;
                }
                header += 31 - (header % 31);


                pending.WriteShortMSB(header);
                if ((state & IS_SETDICT) != 0)
                {
                    int chksum = engine.Adler;
                    engine.ResetAdler();
                    pending.WriteShortMSB(chksum >> 16);
                    pending.WriteShortMSB(chksum & 0xffff);
                }

                state = BUSY_STATE | (state & (IS_FLUSHING | IS_FINISHING));
            }

            for (; ; )
            {
                int count = pending.Flush(output, offset, length);
                offset += count;
                totalOut += count;
                length -= count;

                if (length == 0 || state == FINISHED_STATE)
                {
                    break;
                }

                if (!engine.Deflate((state & IS_FLUSHING) != 0, (state & IS_FINISHING) != 0))
                {
                    if (state == BUSY_STATE)
                    {
                        return origLength - length;
                    }
                    else if (state == FLUSHING_STATE)
                    {
                        if (level != NO_COMPRESSION)
                        {
                            int neededbits = 8 + ((-pending.BitCount) & 7);
                            while (neededbits > 0)
                            {
                                pending.WriteBits(2, 10);
                                neededbits -= 10;
                            }
                        }
                        state = BUSY_STATE;
                    }
                    else if (state == FINISHING_STATE)
                    {
                        pending.AlignToByte();
                        if (!noHeader)
                        {
                            int adler = engine.Adler;
                            pending.WriteShortMSB(adler >> 16);
                            pending.WriteShortMSB(adler & 0xffff);
                        }
                        state = FINISHED_STATE;
                    }
                }
            }
            return origLength - length;
        }

        public void SetDictionary(byte[] dict, int offset, int length)
        {
            if (state != INIT_STATE)
            {
                throw new InvalidOperationException();
            }

            state = SETDICT_STATE;
            engine.SetDictionary(dict, offset, length);
        }
    }
}
